/*
  Copyright (c) 2010
  The Regents of the University of Michigan
  All Rights Reserved

  Permission is granted to use, copy, create derivative works, and
  redistribute this software and such derivative works for any purpose,
  so long as the name of the University of Michigan is not used in
  any advertising or publicity pertaining to the use or distribution
  of this software without specific, written prior authorization. If
  the above copyright notice or any other identification of the
  University of Michigan is included in any copy of any portion of
  this software, then the disclaimer below must also be included.

  This software is provided as is, without representation or warranty
  of any kind either express or implied, including without limitation
  the implied warranties of merchantability, fitness for a particular
  purpose, or noninfringement.  The Regents of the University of
  Michigan shall not be liable for any damages, including special,
  indirect, incidental, or consequential damages, with respect to any
  claim arising out of or in connection with the use of the software,
  even if it has been or is hereafter advised of the possibility of
  such damages.
*/
#include "clearview.h"

#include <sys/time.h>
#include <sys/types.h>
#include <event.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/socket.h>
#include <netdb.h>
#include <netinet/ip.h>

#include <openssl/rand.h>

#include <glib.h>

#include "bootstrap_nodes.h"
#include "clearview_internal.h"
#include "constants.h"
#include "id.h"
#include "lookup.h"
#include "network.h"
#include "serialization.h"
#include "utils.h"

uint32_t next_transaction_id;
struct in_addr local_addr;

static void clearview_handle_recv(int fd, short event_type, void *arg) {
  struct ClearviewInstance *cv = arg;
  char buf[MAX_PACKET_SIZE];
  struct sockaddr_in src_addr;
  struct ContactAddress src_contact;
  socklen_t addrlen = sizeof(src_addr);
  ssize_t bytes_recvd;
  int r;

  if ((bytes_recvd = recvfrom(fd, buf, sizeof(buf), 0,
                              (struct sockaddr *)&src_addr, &addrlen)) == -1) {
    perror("Error in clearview_handle_recv (shouldn't happen):");
    return;
  }

  contact_init(&src_contact);
  src_contact.addr = src_addr.sin_addr;
  src_contact.port = ntohs(src_addr.sin_port);

  logger_log_contact(&cv->log, &src_contact);

  if (buf[0] & 0x80) {
    // Request. Isn't this protocol great?
    r = decode_request(buf, bytes_recvd, cv, &src_contact);
  } else {
    // Reply.
    r = decode_reply(buf, bytes_recvd, cv, &src_contact);
  }

  if (r < 0) {
    // Decode failed; whine about it.
#ifdef VERBOSE_LOG
    fprintf(stderr, "Failed to decode packet:");
    print_hex((unsigned char*)buf, bytes_recvd);
#endif
  }
}


void clearview_init(char *ip_string) {
  char hostname[256];

  event_init();

  RAND_pseudo_bytes((unsigned char *)&next_transaction_id,
                    sizeof(next_transaction_id));

  if (gethostname(hostname, sizeof(hostname)) == -1) {
    fprintf(stderr, "Couldn't get local hostname! Dying.\n");
    exit(1);
  }

  puts(hostname);

  /* Get the local non-loopback IP address. */
  if (inet_pton(AF_INET, ip_string, &local_addr) != 1) {
    fprintf(stderr, "Bogus IP address string %s supplied! Dying.\n", ip_string);
    exit(1);
  }

}


struct ClearviewInstance *clearview_new(uint16_t port) {
  struct ClearviewInstance *ret = g_slice_new(struct ClearviewInstance);
  struct sockaddr_in addr;
  struct ContactAddress new_contact;
  ret->port = port;
  if ((ret->sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
    g_slice_free1(sizeof(struct ClearviewInstance), ret);
    perror("Couldn't create socket");
    exit(1);
    return NULL;
  }

  contact_init(&new_contact);

  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = INADDR_ANY;
  memset(addr.sin_zero, 0, sizeof(addr.sin_zero));

  if (bind(ret->sock, (const struct sockaddr*)&addr, sizeof(addr)) == -1) {
    close(ret->sock);
    g_slice_free1(sizeof(struct ClearviewInstance), ret);
    fprintf(stderr, "Couldn't bind to port!\n");
    return NULL;
  }

  ret->ev = g_slice_alloc(sizeof(*ret->ev));
  event_set(ret->ev, ret->sock, EV_READ | EV_PERSIST, clearview_handle_recv,
            ret);
  if (event_add(ret->ev, NULL) == -1) {
    fprintf(stderr, "libevent error occurred in event_add! Dying.\n");
    exit(1);
  }

  new_contact.addr = local_addr;
  new_contact.port = port;

  // Ownership taken over, so we don't need to free the contact's ID.
  ret->id = clearview_get_id(&new_contact);

  do {
    RAND_pseudo_bytes((unsigned char *)&ret->instance_id,
                      sizeof(ret->instance_id));
  } while(!ret->instance_id); // 0 will confuse other clients.

  logger_init(ret, &ret->log);

  ret->nlookups = 0;
  ret->lookups = g_ptr_array_new();
  return ret;
}


void clearview_free(struct ClearviewInstance *victim) {
  clearview_id_free(victim->id); // Calling id_free in spite of doc is correct.
  close(victim->sock);
  logger_cleanup(&victim->log);
  g_ptr_array_free(victim->lookups, TRUE);
  g_slice_free1(sizeof(struct ClearviewInstance), victim);
}


void clearview_lookup(struct ClearviewInstance *cv, char *id) {
  struct Lookup *lookup = lookup_new();
  g_ptr_array_add(cv->lookups, lookup);
  cv->nlookups++;
  lookup_start(cv, lookup, id);
}

void clearview_join_network(struct ClearviewInstance *cv) {
  // TODO(swolchok): don't bootstrap everyone from the root node.
  g_return_if_fail(cv != NULL);

  clearview_lookup(cv, cv->id);
}
